﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using memcached;

namespace memcached
{
    /// <summary>
    /// A Redis client.
    /// </summary>
    /// <remarks>
    /// Note the Incd/Decr commands are not compatible with Get/Set/Add commands.
    /// The former use strings, the later use binary representations.
    /// </remarks>
    public class RedisCache : ICacheAdvanced, IDisposable
    {
        private ServiceStack.Redis.RedisClient redis;
        private readonly static ServiceStack.Redis.Support.ISerializer serializer =
            new ServiceStack.Redis.Support.OptimizedObjectSerializer();

        /// <summary>
        /// Initialize default client on localhost and port 6379.
        /// </summary>
        [System.Diagnostics.DebuggerStepThrough]
        public RedisCache()
            : this("127.0.0.1", 6379)
        {
        }

        public RedisCache(string host, int port)
        {
            redis = ServiceStack.Redis.RedisClientFactory.Instance.CreateRedisClient(host, port);
        }

        public ServiceStack.Redis.IRedisClient Client
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return redis; }
        }

        public long Increment(string key, long startAt)
        {
            try
            {
                // If the key does not exist, it is set to 0 before performing the operation
                if (startAt != 0)
                {
                    if (redis.Exists(key) <= 0)
                        return redis.Increment(key, (uint)startAt);
                }
                return redis.Increment(key, 1);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return -1;
            }
        }

        public long Decrement(string key)
        {
            try
            {
                // If the key does not exist, it is set to 0 before performing the operation
                return redis.Decrement(key, 1);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return -1;
            }
        }

        public bool Set(string key, ulong cas, object value)
        {
            throw new NotImplementedException("Redis does not support CAS");
        }

        public CasValue<T> Get<T>(string key)
        {
            throw new NotImplementedException("Redis does not support CAS");
        }

        public bool Set(string key, object value)
        {
            byte[] v = serializer.Serialize(value);
            try
            {
                redis.Set(key, v);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
            return true;
        }

        public bool Set(string key, int validFor, object value)
        {
            if (validFor < 1)
                throw new ArgumentOutOfRangeException("validFor");
            byte[] v = serializer.Serialize(value);
            try
            {
                redis.SetEx(key, validFor, v);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
            return true;
        }

        public bool Set(string key, DateTime expireAt, object value)
        {
            byte[] v = serializer.Serialize(value);
            try
            {
                redis.Set<byte[]>(key, v, expireAt);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
            return true;
        }

        public bool Add(string key, int validFor, object value)
        {
            if (validFor < 1)
                throw new ArgumentOutOfRangeException("validFor");
            byte[] v = serializer.Serialize(value);
            try
            {
                return redis.Add<byte[]>(key, v, TimeSpan.FromSeconds(validFor));
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
        }

        public bool Add(string key, DateTime expireAt, object value)
        {
            byte[] v = serializer.Serialize(value);
            try
            {
                return redis.Add<byte[]>(key, v, expireAt);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
        }

        public bool Remove(string key)
        {
            try
            {
                return redis.Remove(key);
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                return false;
            }
        }

        public T Get<T>(string key, out bool success)
        {
            success = true;
            try
            {
                byte[] v = redis.GetBytes(key);
                if (v != null)
                {
                    object value = serializer.Deserialize(v);
                    if (value is T)
                        return (T)value;
                }
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
            }
            return default(T);
        }

        public object Get(string key, out bool success)
        {
            success = true;
            byte[] v = redis.GetBytes(key);
            if (v == null)
                return null;
            try
            {
                object value = serializer.Deserialize(v);
                return value;
            }
            catch (ServiceStack.Redis.RedisException e)
            {
                System.Diagnostics.Trace.TraceError(e.Message);
                success = false;
                return null;
            }
        }

        /// <summary>
        /// Acquire a lock for an item. Call Dnispose to release.
        /// </summary>
        /// <param name="key">not null</param>
        /// <param name="validFor">timeout in seconds</param>
        /// <returns>RedisLock</returns>
        public IDisposable Lock(string key, int validFor)
        {
            return redis.AcquireLock(key, TimeSpan.FromSeconds(validFor));
        }

        public void Dispose()
        {
            if (redis != null)
            {
                GC.SuppressFinalize(this);
                redis.Dispose();
                redis = null;
            }
        }
    }
}